package org.ainlolcat.ainscience.statistics.handlers.hitran;

import org.ainlolcat.ainscience.statistics.handlers.hitran.instfunc.InstrumentalFunction;
import org.ainlolcat.ainscience.utils.ArraysUtil;

import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * @author ainlolcat
 *         Date: 5/1/13
 */
public class SpectrGenerator{
    private double shift = 0;

    static enum SpectrType{
        AX,
        BX
    }

    static class Spectr{

        double T;
        SpectrType type;
        double MO;
        double[] x;
        double[] y;
        private Map<Double,double[]> cache = new WeakHashMap<Double, double[]>();
        List<SpectrLine> spectralLines;
        InstrumentalFunction func;

        Spectr(SpectrType type, double t, double MO, double[] x, double[] y, List<SpectrLine> spectralLines, InstrumentalFunction func) {
            this.type = type;
            T = t;
            this.MO = MO;
            this.x = x;
            this.y = y;
            this.spectralLines = spectralLines;
            this.func = func;
        }

        public double[] getSpectr(double shift){
            Double usedShift = round(shift, HitranConsts.SHIFT_PRECISION);

            if (cache.containsKey(usedShift)){
                return cache.get(usedShift);
            } else if (HitranConsts.USE_SERIALIZATION) {
                double[] deser = deserialize(usedShift);
                if (deser != null) {
                    cache.put(usedShift, deser);
                    return deser;
                }
            }
            double[] answer = new double[y.length];

            //correct answer
            for (int i=0;i<answer.length;i++){
                answer[i] = 0;
                for (SpectrLine line:spectralLines){
                    answer[i] += line.getIntensity(T) * func.getValue(x[i] - line.getCenter() + usedShift);
                }
            }
            cache.put(usedShift, answer);
            if (HitranConsts.USE_SERIALIZATION)
                serialize(usedShift, answer);
            return answer;
        }

        private void serialize(double shift, double[] answer)
        {
            String path = "./.cache/" + type.name();
            File dir = new File(path);
            if (!dir.exists())
                dir.mkdirs();
            File result = new File(path + "/" + T+"_"+shift+".dat");
            try {
                OutputStream os = new FileOutputStream(result);
                ObjectOutputStream oos = new ObjectOutputStream(os);
                oos.writeObject(answer);
                oos.flush();
                os.flush();
                oos.close();
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private double[] deserialize(double shift)
        {
            String path = "./.cache/" + type.name();
            File result = new File(path + "/" + T+"_"+shift+".dat");
            if (!result.exists())
                return null;
            try {
                InputStream os = new FileInputStream(result);
                ObjectInputStream oos = new ObjectInputStream(os);
                double[] data = (double[]) oos.readObject();
                oos.close();
                os.close();
                return data;
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }

        public double round(double value, int places) {
            if (places < 0) throw new IllegalArgumentException();

            BigDecimal bd = new BigDecimal(value);
            bd = bd.setScale(places, RoundingMode.HALF_UP);
            return bd.doubleValue();
        }
    }

    private static Map<SpectrType, Map<Double,Spectr>> caches = new HashMap();
    static {
        caches.put(SpectrType.AX, new WeakHashMap<Double, Spectr>());
        caches.put(SpectrType.BX, new WeakHashMap<Double, Spectr>());
    }


    public double getShift() {
        return shift;
    }

    public double[] getAxSpectr(double T, InstrumentalFunction func, double[] x, double[] y ) throws IOException {
        return getSpectr(T, LineLoader.getInstance().getAxLines(), func ,x,y, SpectrType.AX);
    }

    public double[] getBxSpectr(double T, InstrumentalFunction func, double[] x, double[] y ) throws IOException {
        return getSpectr(T, LineLoader.getInstance().getBxLines(), func,x,y, SpectrType.BX);
    }

    private double[] getSpectr(double T, List<SpectrLine> spectralLines,  InstrumentalFunction func, double[] x, double[] y , SpectrType type){
        Spectr spectr = getSpectr(T, spectralLines, func, x, type);
        double realMO = MO(x, y);
        shift = spectr.MO - realMO;
        return spectr.getSpectr(shift);
    }

    public Spectr getSpectr(double T, List<SpectrLine> spectrLines, InstrumentalFunction func, double[] x, SpectrType type){
        Map<Double, Spectr> cache = caches.get(type);

        if (cache.containsKey(T))
            return cache.get(T);

        double[] answer = new double[x.length];
        //prepare spectr
        for (int i=0;i<answer.length;i++){
            answer[i] = 0;
            for (SpectrLine line:spectrLines){
                answer[i] +=line.getIntensity(T)*func.getValue(x[i] - line.getCenter());
            }
        }
        //check if MO is equal
        double genMO = MO(x, answer);
        Spectr result = new Spectr(type, T, genMO,x, answer, spectrLines, func);
        cache.put(T, result);
        return result;
    }

    float threshold = 0.4f;
    private double MO(double[] x, double[] y){
        double answer = 0 ;
        double summ = 0;
        double max = ArraysUtil.getMax(y) * threshold;
        for (int i=0;i<y.length;i++){
            if (y[i]>max){
                summ += y[i];
            }
        }
        for (int i=0;i<x.length;i++){
            if (y[i]>max){
                answer+=x[i]*y[i]/summ;
            }
        }
        return answer;
    }

    @Deprecated
    public double[][] getAxSpectr(double T, InstrumentalFunction func,double start, double end, double step) throws IOException {
        int count = (int) ((end-start)/step)+1;
        double[][] answer = new double[count][2];
        List<SpectrLine> spectr = LineLoader.getInstance().getAxLines();
        for (int i=0;i<count;i++){
            answer[i][0] = start+i*step;
            answer[i][1] = 0;
            for (SpectrLine line:spectr){
                answer[i][1] +=line.getIntensity(T)*func.getValue(answer[i][0] - line.getCenter());
            }
        }
        return answer;
    }

    @Deprecated
    public double[][] getBxSpectr(double T, InstrumentalFunction func,double start, double end, double step) throws IOException {
        int count = (int) ((end-start)/step)+1;
        double[][] answer = new double[count][2];
        List<SpectrLine> spectr = LineLoader.getInstance().getBxLines();
        for (int i=0;i<count;i++){
            answer[i][0] = start+i*step;
            answer[i][1] = 0;
            for (SpectrLine line:spectr){
                answer[i][1] +=line.getIntensity(T)*func.getValue(answer[i][0] - line.getCenter());
            }
        }
        return answer;
    }
}
